正規表現の変形で作る独自記法WiKi Parser
shokai.icon @shokai
https://gyazo.com/966f89888c2c99d716ea8dad7bacf7a6
右のメニューのStart Presentationでスライドになります 応募した内容
感想など
今日の話
1. 独自記法をどんどん作って乱立させよう
2. その実装方法
元々20分ぐらいライブコーディングで説明してもうまくまとまらなかった話を飛び入りで5分にまとめてリベンジしにきた
https://gyazo.com/8668866112b6ce2a5537e20d9afdbf4c
なぜ独自記法?
ページ間リンクが最も重要な機能だから
1ページ内の装飾よりも
[も(も全部予約されてる
じゃあ全部作ろう
Scrapbox記法
[builderscon]
見た目はhashtagだけど内部的にはリンク記法と同じ
https://gyazo.com/2e639f1da5d8d317130a2c1bd2d3f39b
書いたそばからリンク構造を辿って、推薦が更新されていく
https://gyazo.com/6dc396b96f14603770c7423dbf507810
記法は重要
ドキュメントツールにはコンセプトに最適化した記法が必要
体験が中途半端になるので、リンク記法を最優先したい
必ずしもMarkdownが最適では無いし、そもそもプログラマしか使ってない
例: <img>に<a>を付けたい
Markdown
[![タイトル](画像URL)](リンク先URL)
Scrapbox記法
[画像URL リンク先URL]もしくは
[リンク先URL 画像URL]どっちも可
物を覚えない為にパソコン使ってるんだから順不同で書きたい 互換性とか気にしなくていいのでは?
良いアプリならユーザーが勝手にmarkdownとの変換ツール等を作ってくれる
どんどん分裂していこう
WiKi parserを簡単に作る
正規表現でやる
まじめに1文字ずつ読むようなparserとか書かない
React使うのでclient js側でparseする
処理はclientに分散するので実行速度は無視
サーバーでparseしない
どうやってもReactのrenderよりは軽いので気にしなくていいと思う
基本的な流れ
生テキストがある
今日は[builderscon]に行ったよ
1. 生テキストを正規表現Aでsplitする
記法部分と生テキストに別れる
今日は と [builderscon] と に行ったよになる
2. 記法部分から正規表現Bでmatchする
記法の中からパーツが取れる
[builderscon]からbuildersconを取り出す
3. あとはtreeを返してReactでDOMにすればいい
つらみ
よく似た正規表現AとB、2つ書く必要がある
1つではどうしてもできない
その2つを同時に修正しないとparserが壊れる
このスライドでは簡単な正規表現で説明しているけど
実際は/\[([^\[\]]+)\]/とかなので、2つあると間違える
よく似た正規表現
1. 生テキストから記法とそれ以外を分ける
code:js
2. 記法の中からパーツを取り出す
code:js
'builderscon', // パーツが取れた
index: 0,
ここで重要なのは丸カッコ( )の位置で
1と2で正規表現の中にあったり外にあったりする
これを変換できれば正規表現1つで済むのでは?!
つまり
/\[(.+)\]/
と
/(\[.+\])/
を変換したい
解決方法
正規表現Cを宣言し、AとB2つの正規表現に変換する
flagsとsourceを使うと変形できるじゃん
code:js
undefined
reg.flags
'im'
reg.source
new RegExp('(' + reg.source + ')', reg.flags)
/(\.+\)/im // 前後に丸カッコ付けた新しい正規表現 capture (と) をどうにかする
/\[(.+)\]/
/(\[.+\])/
を変換したいが、丸カッコを単純に削除してしまうと壊れる正規表現がある
(a|b)とか
丸カッコを無効化すると良さそうだ
(a|b)を(?:a|b)にする
?:を付けると、グループ化はするけどmatchには出てこないようになる
/\[(.+)\]/を
/\[(?:.+)\]/にしてから、
/(\[(?:.+)\])/にするといい
sourceを地道にreplaceしてnon-capturing groupsにした正規表現を返す
code:js
// disable "capture" in RegExp
// replace (~~) with (?:~~)
module.exports = function (regexp) {
return regexp
.source
.replace(/\(\((?!\?)/g, function (leftParenthesis) {
return leftParenthesis + '?:'
})
.replace(/(^|^\\)\((?!\?)/g, function (leftParenthesis) { return leftParenthesis + '?:'
})
}
これで2つの正規表現が変換で作れるようになった
npmにした
https://gyazo.com/5619c57610cde3ee9a03a60769bc8f05 https://www.npmjs.com/package/disable-regexp-capture
最終型
ここから色々やると、1つの正規表現を書くだけでparserが作れるようになる
code:js
export const parsePageLink = createNodeParser(/\[([^\\]+)\]/, (page) => ({ type: 'pageLink',
page: title
}))
まとめ
正規表現1つ書けばよくなってメンテナンス性が上がった